package br.ufrgs.rescue.agent;

import br.ufrgs.rescue.model.BlockedStreet;
import br.ufrgs.rescue.model.BuildingOnFire;
import br.ufrgs.rescue.model.CivilianInjured;
import br.ufrgs.rescue.model.MyMessage;
import br.ufrgs.rescue.util.MySearch;
import br.ufrgs.rescue.util.Serializer;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
import java.util.Map;

import rescuecore2.worldmodel.EntityID;
import rescuecore2.Constants;
import rescuecore2.log.Logger;
import rescuecore2.messages.Command;

import rescuecore2.standard.components.StandardAgent;
import rescuecore2.standard.entities.Area;
import rescuecore2.standard.entities.StandardEntity;
import rescuecore2.standard.entities.Building;
import rescuecore2.standard.entities.Civilian;
import rescuecore2.standard.entities.Refuge;
import rescuecore2.standard.entities.Road;
import rescuecore2.standard.entities.Human;
import rescuecore2.standard.entities.StandardEntityURN;
import rescuecore2.standard.kernel.comms.ChannelCommunicationModel;
import rescuecore2.standard.kernel.comms.StandardCommunicationModel;
import rescuecore2.standard.messages.AKSpeak;
import rescuecore2.worldmodel.Entity;

/**
 * Abstract base class for sample agents.
 *
 * @param <E> The subclass of StandardEntity this agent wants to control.
 */
public abstract class MyAbstractAgent<E extends StandardEntity> extends StandardAgent<E> {

    private static final int RANDOM_WALK_LENGTH = 50;
    private static final String SAY_COMMUNICATION_MODEL = StandardCommunicationModel.class.getName();
    private static final String SPEAK_COMMUNICATION_MODEL = ChannelCommunicationModel.class.getName();
    /**
     * The search algorithm.
     */
    protected MySearch search;
    /**
     * Whether to use AKSpeak messages or not.
     */
    protected boolean useSpeak;
    /**
     * Cache of building IDs.
     */
    protected List<EntityID> buildingIDs;
    /**
     * Cache of road IDs.
     */
    protected List<EntityID> roadIDs;
    /**
     * Cache of refuge IDs.
     */
    protected List<EntityID> refugeIDs;
    private Map<EntityID, Set<EntityID>> neighbours;
    //Lista de Bloqueios
    protected List<BlockedStreet> blockedSreets;
    //Lista de Prédios em chamas
    protected List<BuildingOnFire> buildingsOnFire;
    //Lista de Civis Soterrados/Feridos
    protected List<CivilianInjured> civiliansInjurieds;
    //Tratador de Mensagens
    private Serializer serializer;
    //Alvo Atual
    protected EntityID current_target = null;
    protected List<BuildingOnFire> buildingsToPrint = new ArrayList<>();

    /**
     * Construct an MyAbstractAgent.
     */
    protected MyAbstractAgent() {
    }

    @Override
    protected void postConnect() {
        super.postConnect();
        buildingIDs = new ArrayList<EntityID>();
        roadIDs = new ArrayList<EntityID>();
        refugeIDs = new ArrayList<EntityID>();
        //Meus atributos
        blockedSreets = new ArrayList<BlockedStreet>();
        buildingsOnFire = new ArrayList<BuildingOnFire>();
        civiliansInjurieds = new ArrayList<CivilianInjured>();
        serializer = new Serializer();
        current_target = null;

        for (StandardEntity next : model) {
            if (next instanceof Building) {
                buildingIDs.add(next.getID());
            }
            if (next instanceof Road) {
                roadIDs.add(next.getID());
            }
            if (next instanceof Refuge) {
                refugeIDs.add(next.getID());
            }
        }
        search = new MySearch(model);
        neighbours = search.getGraph();
        useSpeak = config.getValue(Constants.COMMUNICATION_MODEL_KEY).equals(SPEAK_COMMUNICATION_MODEL);
        Logger.debug("Communcation model: " + config.getValue(Constants.COMMUNICATION_MODEL_KEY));
        Logger.debug(useSpeak ? "Using speak model" : "Using say model");
    }

    /**
     * Construct a random walk starting from this agent's current location to a
     * random building.
     *
     * @return A random walk.
     */
    protected List<EntityID> randomWalk() {
        List<EntityID> result = new ArrayList<EntityID>(RANDOM_WALK_LENGTH);
        Set<EntityID> seen = new HashSet<EntityID>();
        EntityID current = ((Human) me()).getPosition();
        for (int i = 0; i < RANDOM_WALK_LENGTH; ++i) {
            result.add(current);
            seen.add(current);
            List<EntityID> possible = new ArrayList<EntityID>(neighbours.get(current));
            Collections.shuffle(possible, random);
            boolean found = false;
            for (EntityID next : possible) {
                if (seen.contains(next)) {
                    continue;
                }
                current = next;
                found = true;
                break;
            }
            if (!found) {
                // We reached a dead-end.
                break;
            }
        }
        return result;
    }

    protected void sendMesssage(int time, int channel, MyMessage message) {
        byte[] speak = null;
        try {
            speak = serializer.doIt(message);
            sendSpeak(time, channel, speak);
            System.out.print("\tMensagem enviado com sucesso");
        } catch (IOException e) {
            System.out.print("\tIoException ao gerar mensagem de predio em chamas" + e.getMessage());
            System.out.println("");
            System.out.println("" + e.toString());
            System.out.println("" + e.getLocalizedMessage());
            System.out.println("" + e.fillInStackTrace().getMessage());

        }
    }

    protected void heardMessage(Collection<Command> heard) {
        List<MyMessage> msgs = new ArrayList<>();
        for (Command next : heard) {
            if (next instanceof AKSpeak) {
                byte[] msg = ((AKSpeak) next).getContent();
                try {
                    Object object = serializer.undoIt(msg);
                    if (object instanceof BuildingOnFire) {
                        insertBuilding(new BuildingOnFire((BuildingOnFire) object));
                    } else if (object instanceof BlockedStreet) {
                        blockedSreets.add(((BlockedStreet) object));
                    } else if (object instanceof CivilianInjured) {
                        civiliansInjurieds.add(((CivilianInjured) object));
                    } else {
//                        System.out.println("Não entrou em nada");
                    }
                } catch (IOException e) {
                    Logger.error("Não entendi a mensagem!" + e.getMessage());
                } catch (ClassNotFoundException e) {
                    Logger.error("Mensagem veio com classe que não conheço.");
                }
            }
        }
    }

    protected List<EntityID> randomWalkonStreet() {
        List<EntityID> result = new ArrayList<EntityID>(RANDOM_WALK_LENGTH);
        Set<StandardEntity> seen = new HashSet<StandardEntity>();
        StandardEntity current = location();
        for (int i = 0; i < RANDOM_WALK_LENGTH; ++i) {
            result.add(current.getID());
            seen.add(current);
            List<StandardEntity> neighbours = new ArrayList<StandardEntity>(findNeighbours(current));
            Collections.shuffle(neighbours, random);
            boolean found = false;
            for (StandardEntity next : neighbours) {
                if (seen.contains(next)) {
                    continue;
                }
                if (next instanceof Building && i < RANDOM_WALK_LENGTH - 1) {
                    if (((Building) next).isOnFire()) {
                        continue;
                    }
                }
                current = next;
                found = true;
                break;
            }
            if (!found) {
                break;
            }
        }
        return result;
    }

    public Collection<StandardEntity> findNeighbours(StandardEntity e) {
        Collection<StandardEntity> result = new ArrayList<StandardEntity>();
        if (e instanceof Area) {
            Area a = (Area) e;
            for (EntityID next : a.getNeighbours()) {
                result.add(model.getEntity(next));
            }
        }
        return result;
    }

    protected void insertBuilding(BuildingOnFire b) {
        if (current_target != null) {
            if (b.getId().getValue() != current_target.getValue()) {
                for (int i = 0; i < buildingsOnFire.size(); i++) {
                    // alterado para manipular dados.
                    if (buildingsOnFire.get(i).getId().getValue() == b.getId().getValue()) {
                        buildingsOnFire.get(i).setComposition(b.getComposition());
//                buildingsOnFire.get(i).setWater(b.getWater());
                        buildingsOnFire.get(i).setFieryness(b.getFieryness());
                        buildingsOnFire.get(i).setTemperature(b.getTemperature());
                        buildingsOnFire.get(i).setTotalArea(b.getTotalArea());
                        buildingsOnFire.get(i).setCivilians(civilliansInBuilding(b.getId()));
                        System.out.print("\tatualizou prédio em chamas");
                        return;
                    }
                }
                System.out.print("\tencontrou prédio em chamas e inseriu");
                buildingsOnFire.add(b);
                buildingsToPrint.add(b);
            }
        } else {
            buildingsOnFire.add(b);
            buildingsToPrint.add(b);
        }
    }

    public int civilliansInBuilding(EntityID id) {
        int civis = 0;
        Collection<StandardEntity> civisOnSight = model.getEntitiesOfType(StandardEntityURN.CIVILIAN);
        for (StandardEntity civil : civisOnSight) {
            if (((Civilian) civil).getPosition().equals(id)) {
                civis++;
            }
        }
        return civis;
    }
}